package com.xingchi.tornado.mybatisplus.plugins;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * MyBatis-Plus数据加解密拦截器
 * 用于处理数据库敏感字段的自动加解密
 *
 * @author xingchi
 * @date 2024/7/23 10:30
 */
@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class EncryptDecryptInterceptor implements Interceptor {

    /**
     * 加密字段缓存，避免重复反射获取
     */
    private static final Map<Class<?>, List<Field>> ENCRYPT_FIELD_CACHE = new ConcurrentHashMap<>();

    /**
     * 加密算法实现
     */
    private final IEncryptor encryptor;

    public EncryptDecryptInterceptor(IEncryptor encryptor) {
        this.encryptor = encryptor;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        // 获取SQL类型
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        if (SqlCommandType.INSERT == sqlCommandType || SqlCommandType.UPDATE == sqlCommandType) {
            // 插入或更新操作，需要加密
            this.handleEncrypt(metaObject);
        } else if (SqlCommandType.SELECT == sqlCommandType) {
            // 查询操作，记录需要解密的对象
            // 解密将在结果集处理时进行
        }

        return invocation.proceed();
    }

    /**
     * 处理加密操作
     */
    private void handleEncrypt(MetaObject metaObject) {
        // 获取参数对象
        Object parameterObject = metaObject.getValue("delegate.boundSql.parameterObject");
        if (parameterObject == null) {
            return;
        }

        // 处理单个对象
        if (isEncryptObject(parameterObject)) {
            encryptObject(parameterObject);
        }
        // 处理参数映射
        else if (parameterObject instanceof Map) {
            Map<String, Object> paramMap = (Map<String, Object>) parameterObject;
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                Object paramValue = entry.getValue();
                if (paramValue != null && isEncryptObject(paramValue)) {
                    encryptObject(paramValue);
                }
            }
        }
    }

    /**
     * 判断对象是否需要加密处理
     */
    private boolean isEncryptObject(Object object) {
        List<Field> fields = getEncryptFields(object.getClass());
        return !CollectionUtils.isEmpty(fields);
    }

    /**
     * 获取需要加密的字段
     */
    private List<Field> getEncryptFields(Class<?> clazz) {
        // 先从缓存中获取
        List<Field> fields = ENCRYPT_FIELD_CACHE.get(clazz);
        if (fields != null) {
            return fields;
        }

        // 如果缓存中没有，则扫描字段
        fields = new ArrayList<>();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Encrypt.class)) {
                field.setAccessible(true);
                fields.add(field);
            }
        }

        // 放入缓存
        ENCRYPT_FIELD_CACHE.put(clazz, fields);
        return fields;
    }

    /**
     * 加密对象的敏感字段
     */
    private void encryptObject(Object object) {
        List<Field> fields = getEncryptFields(object.getClass());
        for (Field field : fields) {
            try {
                Object fieldValue = field.get(object);
                if (fieldValue instanceof String && !StringUtils.isEmpty(fieldValue)) {
                    // 只处理非空字符串
                    String encryptedValue = encryptor.encrypt((String) fieldValue);
                    field.set(object, encryptedValue);
                }
            } catch (Exception e) {
                log.error("加密字段时发生异常: {}", field.getName(), e);
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 加解密器接口
     */
    public interface IEncryptor {
        /**
         * 加密方法
         *
         * @param content 待加密的内容
         * @return 加密后的内容
         */
        String encrypt(String content);

        /**
         * 解密方法
         *
         * @param content 待解密的内容
         * @return 解密后的内容
         */
        String decrypt(String content);
    }
}
